﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;


namespace Skybot.BaseSocket
{
    /// <summary>
    /// 异步 UDP Server,需要指定邦定端口,缓冲区设置在配置文件中设置 Protocol_MaxReceivePacketSize
    /// </summary>
    public class UDPAsyncServer
    {



        #region 服务设置相关重要字段

        /// <summary>
        /// 服务处理
        /// </summary>
        public Action<SocketAsyncEventArgs, EndPoint> Handler { get; set; }

        /// <summary>
        /// 服务名
        /// </summary>
        public string ServerName { get; set; }

        /// <summary>
        /// 正在侦听的socket
        /// </summary>
        private Socket _listeningSocket;

        /// <summary>
        /// 监听用的 服务当前正在监听 Socket
        /// </summary>
        public Socket ListeningSocket
        {
            get { return _listeningSocket; }
        }

        private int _port;
        /// <summary>
        /// 端口服务设置
        /// </summary>
        public int Port
        {
            get { return _port; }
            protected set
            {
                _port = value;

            }
        }
        /// <summary>
        /// UDP接收字节数最大一包64KB
        /// </summary>
        private int receiveMaxBuffSize = 64*1024;
        /// <summary>
        /// 获取或者设置最大,数据接收缓冲区
        /// </summary>
        public int ReceiveMaxBuffSize
        {
            get { return receiveMaxBuffSize; }
            set
            {
                //在运行中不能进行绥冲区设置
                if (!ISRuning)
                {
                    receiveMaxBuffSize = value;
                }
                else
                {
                    throw new NotSupportedException("在运行中不能进行绥冲区大小设置");
                }
            }
        }
        /// <summary>
        /// 邦定的IP
        /// </summary>
        private System.Net.IPAddress ipAddress;
        /// <summary>
        /// 邦定的IP
        /// </summary>
        public System.Net.IPAddress IpAddress
        {
            get { return ipAddress; }
            protected set { ipAddress = value; }
        }

        #endregion



        #region 构造函数
        /// <summary>
        /// 创建一个UDP服务监听对像
        /// </summary>
        /// <param name="BindPort">邦定端口</param>
        /// <param name="_ipAddress">IP地址</param>
        public UDPAsyncServer(System.Net.IPAddress _ipAddress, int BindPort)
        {
            IpAddress = _ipAddress;
            Port = BindPort;
        }
        /// <summary>
        /// 创建一个UDP服务监听对像
        /// </summary>
        /// <param name="bindPort">邦定端口</param>
        public UDPAsyncServer(int bindPort)
        {
            IpAddress = IPAddress.Any;
            Port = bindPort;
        }

        /// <summary>
        /// 程序是否正在运行
        /// </summary>
        public bool ISRuning { get; protected set; }


        #endregion

        #region 启动服务

        /// <summary>
        /// 开始服务
        /// </summary>
        public void Open()
        {
            StartListen();
            ISRuning = true;
        }



        #endregion

        #region 数据接收：StartListen|StartReceive|IO_ReceiveComleted|ProcessReceive

        /// <summary>
        /// 启动侦听，并初始化接收workitem
        /// </summary>
        private void StartListen()
        {

            _listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            //侦听所有IP
            IPEndPoint localEP = new IPEndPoint(IpAddress, Port);

            _listeningSocket.Bind(localEP);
            _listeningSocket.DontFragment = true;


            //使用三个IO线程同时接收UDP数据 2014-04-02 被告知,需要开启三个线程进行数据接收
            //开启三个IO线程进行数据接收处理
            System.Threading.ThreadPool.UnsafeQueueUserWorkItem(o =>
                                                                    {
                                                                        RevleReceive();
                                                                        RevleReceive();
                                                                        RevleReceive();
                                                                    }, null);
        }

        /// <summary>
        /// 开始接数据
        /// </summary>
        protected void RevleReceive()
        {
            //创建一个新的Socket 异步接收对像,用于接收数据
            SocketAsyncEventArgs socketArgs = new SocketAsyncEventArgs();
            //设置数据绥冲区是最大接收字节数的20倍
            socketArgs.SetBuffer(new byte[ReceiveMaxBuffSize * 20], 0, ReceiveMaxBuffSize);
            //设置数据接收完成项
            socketArgs.Completed += IO_ReceiveComleted;
            socketArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
            //开始接收数据
            StartReceive(socketArgs);

        }



        /// <summary>
        /// 开始接收
        /// </summary>
        private void StartReceive(SocketAsyncEventArgs socketArgs)
        {
            try
            {
                //同步完成按照msdn解释不会调用Comleted事件
                if (!_listeningSocket.ReceiveFromAsync(socketArgs))
                {
                    //同步或者异步完成都是调用此方法处理
                    IO_ReceiveComleted(this, socketArgs);
                }
            }
            catch (Exception err)
            {

                Console.WriteLine("UDPAsyncServer.StartReceive(object state)　方法执行失败。错误：" + err);
            }
        }

        /// <summary>
        /// 接收完成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void IO_ReceiveComleted(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
                {
                    //转到接收数据
                    ProcessReceive(e);
                }
            }
            catch (Exception err)
            {
                Console.WriteLine("UDPServerHelper.BeginRecive（）　方法执行失败。错误：" + err);
            }
            finally
            {
                StartReceive(e);
            }

        }

        /// <summary>
        /// 处理接收到的数据
        /// </summary>
        /// <param name="e">接收数据SAEA对象</param>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            //1.0 根据接收数据远程终结点得到或者创建对应的通道
            //客户端终结点,这个数据是一定要保存的
            EndPoint clientEndPoint = e.RemoteEndPoint;

            if (Handler != null)
            {
                //客户端终结点
                //转到外部进行处理
                Handler(e, clientEndPoint);
            }

        }

        #endregion







        #region IDisposable

        /// <summary>
        /// 资源是否已经释放
        /// </summary>
        private bool _isDisposed;

        /// <summary>
        /// 当前服务是否已经关闭了
        /// </summary>
        public bool IsDisposed
        {
            get { return _isDisposed; }
        }




        /// <summary>
        /// 必须，以备程序员忘记了显式调用Dispose方法
        /// </summary>
        ~UDPAsyncServer()
        {
            //必须为false
            Dispose(false);
        }

        /// <summary>
        /// 实现IDisposable中的Dispose方法
        /// </summary>
        public void Dispose()
        {

            //必须为true
            Dispose(true);
            //程序不再运行了
            ISRuning = false;
            //通知垃圾回收机制不再调用终结器（析构器）
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 释放相关资源
        /// </summary>
        /// <param name="disposing">是否主动调用Dispose()</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_listeningSocket")]
        private void Dispose(bool disposing)
        {
            if (_isDisposed) return;
            _isDisposed = true;
            if (disposing)
            {
                _listeningSocket.Shutdown(SocketShutdown.Both);
                //5.0 释放侦听端口
                //SocketHelper.ReleaseSocket(_listeningSocket);
                //清理托管资源字段（包含了非托管资源的内部对象，字段实现了IDispose接口）
                _listeningSocket.Dispose();
            }
        }



        #endregion

    }
}
